package se.statifyme.server.declaration;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.bind.annotation.XmlTransient;

import se.statifyme.server.FilterMatcher;
import se.statifyme.server.FilterMatcher.Matcher;
import se.statifyme.server.entities.Item;
import se.statifyme.shared.StringUtils;

public enum ItemDeclarationGenerator {
	INSTANCE;

	private final FilterMatcher ignoredFields;

	private ItemDeclarationGenerator() {
		ignoredFields = new FilterMatcher(Matcher.REGEXP);
		ignoredFields.addFilterPattern("jdo.*");
	}

	public <E extends Item> ItemDeclaration generate(Class<E> clazz) {
		if (clazz == null) {
			throw new IllegalArgumentException("Item class must not be null");
		}
		ItemType itemType = clazz.getAnnotation(ItemType.class);
		if (itemType == null) {
			throw new IllegalArgumentException("Item must be annotated with " + ItemType.class.getName());
		}
		ItemDeclaration result = new ItemDeclaration();
		if (StringUtils.isNotBlank(itemType.displayName())) {
			result.setDisplayName(itemType.displayName());
		} else {
			result.setDisplayName(clazz.getSimpleName());
		}
		result.setDisplayName(itemType.displayName());
		
		List<ItemDeclaration.Field> fields = createItemFields(clazz);
		result.setFields(fields);

		return result;
	}

	private List<ItemDeclaration.Field> createItemFields(Class<?> clazz) {
		List<ItemDeclaration.Field> result = new LinkedList<ItemDeclaration.Field>();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if (includeField(field)) {
				result.add(createItemField(field));
			}
		}
		
		Class<?> superclass = clazz.getSuperclass();
		if (superclass != null && superclass != Item.class.getSuperclass()) {
			result.addAll(createItemFields(superclass));
		}

		return result;
	}

	private boolean includeField(Field field) {
		boolean result = false;
		String name = field.getName();
		if (!ignoredFields.isMatch(name) && field.getAnnotation(XmlTransient.class) == null) {
			if (field.getAnnotation(ItemField.class) == null) {
				Logger.getLogger("StatifyMe").log(Level.SEVERE,
						"Field " + field.getDeclaringClass().getName() + "#" + field.getName() + " is not annotated with " + ItemField.class.getName() + " and will be ignored.");
			} else {
				result = true;
			}
		}
		return result;
	}

	private ItemDeclaration.Field createItemField(Field field) {
		final String type = field.getType().getName();
		final String id = field.getName();
		final ItemField itemField = field.getAnnotation(ItemField.class);
		if (StringUtils.isBlank(id)) {
			throw new IllegalArgumentException("Field ID must not be blank");
		}
		if (StringUtils.isBlank(type)) {
			throw new IllegalArgumentException("Field type must not be blank");
		}
		if (itemField == null) {
			throw new IllegalArgumentException("Field " + field.getDeclaringClass().getName() + "#" + field.getName() + " must be annotated with " + ItemField.class.getName());
		}
		
		ItemDeclaration.Field result = new ItemDeclaration.Field(id, type);
		
		final String displayName;
		if (StringUtils.isBlank(itemField.displayName())) {
			Logger.getLogger("StatifyMe").log(Level.WARNING, "Item field display name not set for field '" + id + "'");
			displayName = id;
		} else {
			displayName = itemField.displayName();				
		}
		result.setDisplayName(displayName);
		result.setIdField(itemField.isId());
		result.setInternal(itemField.internal());
		result.setOptional(itemField.optional());
		
		return result;
	}

}
